﻿using ReoGrid.Mvvm.Interfaces;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static RunningCalculator.Utility;

namespace RunningCalculator
{
    class PaceCalculator
    {
        private void NodeExistCheck(HashSet<Utility.DistanceGap> distanceGapSet, double beginning, double end)
        {
            bool alreadyHave = false;
            foreach (var item in distanceGapSet)
            {
                if (Math.Abs(beginning - item.GapBeginning) < 0.000001 && Math.Abs(end - item.GapEnd) < 0.000001)
                {
                    alreadyHave = true;
                    break;
                }
            }
            if (!alreadyHave)
            {
                bool nodeInserted = false;
                foreach (var item in distanceGapSet)
                {
                    if(item.GapBeginning == beginning)
                    {
                        if(item.GapEnd > end)
                        {
                            distanceGapSet.Add(new Utility.DistanceGap { GapBeginning = beginning, GapEnd = end });
                            distanceGapSet.Add(new Utility.DistanceGap { GapBeginning = end, GapEnd = item.GapEnd });

                            distanceGapSet.Remove(item);

                            nodeInserted = true;
                            break;
                        }
                        else
                        {
                            distanceGapSet.Add(new Utility.DistanceGap { GapBeginning = item.GapEnd, GapEnd = end });
                            nodeInserted = true;
                            break;
                        }
                    }
                }

                if(!nodeInserted)
                    distanceGapSet.Add(new Utility.DistanceGap{ GapBeginning = beginning, GapEnd= end });
            }
        }

        private void ComputeNodeGap(HashSet<Utility.DistanceGap> distanceGapSet, List<double> firstPositionList, List<double> secondPositionList)
        {
            if ((firstPositionList == null || firstPositionList.Count == 0) && secondPositionList != null)
            {
                secondPositionList = secondPositionList.Distinct().ToList();
                secondPositionList.Sort();
                for (int j = secondPositionList.Count-1; j >0; --j)
                {
                    NodeExistCheck(distanceGapSet, secondPositionList[j-1], secondPositionList[j]);
                }
            }
            else if(firstPositionList != null && secondPositionList != null)
            {
                firstPositionList = firstPositionList.Distinct().ToList();
                firstPositionList.Sort();

                secondPositionList = secondPositionList.Distinct().ToList();
                secondPositionList.Sort();
                for (int j = 0; j < secondPositionList.Count; ++j)
                {
                    if (secondPositionList[j] == 0)
                        continue;
                    for (int i = firstPositionList.Count - 1; i >= 0; --i)
                    {
                        if (firstPositionList[i] < secondPositionList[j])
                        {
                            NodeExistCheck(distanceGapSet, firstPositionList[i], secondPositionList[j]);
                            break;
                        }
                    }
                }
            }
        }

        private void ComputeGapList(HashSet<Utility.DistanceGap> distanceGapSet, List<double> posList, double distance, double gap)
        {
            List<double> gapList = new List<double>();

            for (double i = 0; i <= distance; i=i+gap)
                gapList.Add(i);

            ComputeNodeGap(distanceGapSet, posList, gapList);
            posList.AddRange(gapList);
        }

        public void Compute(Dictionary<Utility.GapCheckedStatus, bool> checkStatusDict, List<SectionSetting> sectionSettingList, double distance, ObservableCollection<IRecordModel> paceDataList)
        {
            HashSet<Utility.DistanceGap> distanceGapSet = new HashSet<DistanceGap>();
            List<double> calculatePosition = new List<double>();

            //1km
            if (checkStatusDict.ContainsKey(Utility.GapCheckedStatus.Radio_1km_Checked) && checkStatusDict[Utility.GapCheckedStatus.Radio_1km_Checked])
                ComputeGapList(distanceGapSet, calculatePosition, distance, 1);

            //2km
            if (checkStatusDict.ContainsKey(Utility.GapCheckedStatus.Radio_2km_Checked) && checkStatusDict[Utility.GapCheckedStatus.Radio_2km_Checked])
                ComputeGapList(distanceGapSet, calculatePosition, distance, 2);

            //3km
            if (checkStatusDict.ContainsKey(Utility.GapCheckedStatus.Radio_3km_Checked) && checkStatusDict[Utility.GapCheckedStatus.Radio_3km_Checked])
                ComputeGapList(distanceGapSet, calculatePosition, distance, 3);

            //4km
            if (checkStatusDict.ContainsKey(Utility.GapCheckedStatus.Radio_4km_Checked) && checkStatusDict[Utility.GapCheckedStatus.Radio_4km_Checked])
                ComputeGapList(distanceGapSet, calculatePosition, distance, 4);

                //5km
            if (checkStatusDict.ContainsKey(Utility.GapCheckedStatus.Radio_5km_Checked) && checkStatusDict[Utility.GapCheckedStatus.Radio_5km_Checked])
                ComputeGapList(distanceGapSet, calculatePosition, distance, 5);

            //6km
            if (checkStatusDict.ContainsKey(Utility.GapCheckedStatus.Radio_6km_Checked) && checkStatusDict[Utility.GapCheckedStatus.Radio_6km_Checked])
                ComputeGapList(distanceGapSet, calculatePosition, distance, 6);

            //7km
            if (checkStatusDict.ContainsKey(Utility.GapCheckedStatus.Radio_7km_Checked) && checkStatusDict[Utility.GapCheckedStatus.Radio_7km_Checked])
                ComputeGapList(distanceGapSet, calculatePosition, distance, 7);

            //8km
            if (checkStatusDict.ContainsKey(Utility.GapCheckedStatus.Radio_8km_Checked) && checkStatusDict[Utility.GapCheckedStatus.Radio_8km_Checked])
                ComputeGapList(distanceGapSet, calculatePosition, distance, 8);

            //9km
            if (checkStatusDict.ContainsKey(Utility.GapCheckedStatus.Radio_9km_Checked) && checkStatusDict[Utility.GapCheckedStatus.Radio_9km_Checked])
                ComputeGapList(distanceGapSet, calculatePosition, distance, 9);

            //show half marathon position
            List<double> marathonList = null;
            if (checkStatusDict.ContainsKey(Utility.GapCheckedStatus.Radio_HalfMarathon_Checked) && 
                checkStatusDict[Utility.GapCheckedStatus.Radio_HalfMarathon_Checked])
                marathonList = new List<double>() {0, 21.0975 };

            //show full marathon position
            if (checkStatusDict.ContainsKey(Utility.GapCheckedStatus.Radio_Marathon_Checked) && 
                checkStatusDict[Utility.GapCheckedStatus.Radio_Marathon_Checked])
            {
                if (marathonList == null)
                    marathonList = new List<double>() {0, 42.195 };
                else
                    marathonList.Add(42.195);
            }
 
            if(marathonList != null)
            { 
                ComputeNodeGap(distanceGapSet, calculatePosition, marathonList);
                calculatePosition.AddRange(marathonList);
            }

            //half marathon, marathon and section settings 
            if (sectionSettingList != null)
            {
                List<double> sectionPositionList = new List<double>();

                foreach (var item in sectionSettingList)
                {
                    if (item.Section.GapBeginning > distance)
                        break;
                    else if (item.Section.GapEnd > distance)
                    {
                        sectionPositionList.Add(item.Section.GapBeginning);
                        sectionPositionList.Add(distance);
                        break;
                    }
                    else
                    {
                        sectionPositionList.Add(item.Section.GapBeginning);
                        sectionPositionList.Add(item.Section.GapEnd);
                    }
                }
                sectionPositionList.Add(distance);
                ComputeNodeGap(distanceGapSet, calculatePosition, sectionPositionList);
            }

            ComputeSectionPace(sectionSettingList, distanceGapSet, paceDataList);
        }

        private void ComputeSectionPace(List<SectionSetting> sectionSettingList, HashSet<Utility.DistanceGap> distanceGapSet, ObservableCollection<IRecordModel> paceDataList)
        {
            List<DistanceGap> gapNodeList = new List<DistanceGap>();
            gapNodeList.AddRange(distanceGapSet);
            gapNodeList.Sort((a, b) => a.GapBeginning.CompareTo(b.GapBeginning));

            double totalUsedTime = 0;
            double passedDistance = 0;
            int currentSettingPanelIndex = 0;
            double currentRemainSettingDistance = 0;
            double currentRemainSettingPace = 0;

            for(int i=0; i< gapNodeList.Count; ++i)
            {
                double gapBeginnig = gapNodeList[i].GapBeginning;
                double gapEnd = gapNodeList[i].GapEnd;
                double gapLength = gapEnd - gapBeginnig;

                double currentGapTotalUsedTime = 0;

                while (gapLength > 0)
                {
                    if (currentRemainSettingDistance > 0)
                    {
                        if (currentRemainSettingDistance >= gapLength)
                        {
                            currentGapTotalUsedTime += gapLength * currentRemainSettingPace;
                            currentRemainSettingDistance = currentRemainSettingDistance - gapLength;
                            gapLength = 0;
                            break;
                        }
                        else
                        {
                            currentGapTotalUsedTime += currentRemainSettingDistance * currentRemainSettingPace;
                            gapLength = gapLength - currentRemainSettingDistance;
                            currentRemainSettingPace = 0;
                            currentRemainSettingDistance = 0;
                        }
                    }
                    else
                    {
                        for (int index = currentSettingPanelIndex; index < sectionSettingList.Count; ++index)
                        {
                            double panelSettingSectionLength = sectionSettingList[index].Section.GapEnd - sectionSettingList[index].Section.GapBeginning;
                            if (panelSettingSectionLength >= gapLength)
                            {
                                currentGapTotalUsedTime += gapLength * sectionSettingList[index].Pace;
                                currentRemainSettingPace = sectionSettingList[index].Pace;
                                currentRemainSettingDistance = panelSettingSectionLength - gapLength;

                                currentSettingPanelIndex = index + 1;
                                gapLength = 0;
                                break;
                            }
                            else
                            {
                                currentGapTotalUsedTime += panelSettingSectionLength * sectionSettingList[index].Pace;
                                gapLength = gapLength - panelSettingSectionLength;
                                currentRemainSettingPace = 0;
                                currentRemainSettingDistance = 0;
                            }
                        }
                    }
                }

                totalUsedTime += currentGapTotalUsedTime;
                passedDistance += gapEnd - gapBeginnig;

                double sectionPace = Math.Round(currentGapTotalUsedTime / (gapEnd - gapBeginnig), 6);
                double totalPace = Math.Round(totalUsedTime / passedDistance, 6);

                PaceModel paceData = new PaceModel()
                {
                    Index = (i+1).ToString(),
                    DistanceBeginning = Utility.ConvertDoubleToStr( gapBeginnig),
                    DistanceEnd = Utility.ConvertDoubleToStr(gapEnd),
                    SectionLength = Utility.ConvertDoubleToStr(gapEnd - gapBeginnig),
                    TotalLength = Utility.ConvertDoubleToStr(passedDistance),
                    SectionPace = Utility.ConvertSecondsToHourMinuteSecond((uint)sectionPace, true),
                    Pace = Utility.ConvertSecondsToHourMinuteSecond((uint)totalPace, true),
                    SectionTime = Utility.ConvertSecondsToHourMinuteSecond((uint)currentGapTotalUsedTime, false),
                    TotalTime = Utility.ConvertSecondsToHourMinuteSecond((uint)totalUsedTime, false)
                };
                paceDataList.Add(paceData);

                if(passedDistance == 21.0975 || passedDistance == 42.195)
                {
                    PaceModel specialNode = new PaceModel()
                    {
                        Index = passedDistance == 21.0975?"半马":"全马",
                        Pace = Utility.ConvertSecondsToHourMinuteSecond((uint)totalPace, true),
                        TotalLength = Utility.ConvertDoubleToStr(passedDistance),
                        TotalTime = Utility.ConvertSecondsToHourMinuteSecond((uint)totalUsedTime, false)
                    };
                    paceDataList.Add(specialNode);
                }

                if(i == gapNodeList.Count -1 && passedDistance != 21.0975 && passedDistance != 42.195)
                {
                    PaceModel specialNode = new PaceModel()
                    {
                        Index = "终点",
                        Pace = Utility.ConvertSecondsToHourMinuteSecond((uint)totalPace, true),
                        TotalLength = Utility.ConvertDoubleToStr(passedDistance),
                        TotalTime = Utility.ConvertSecondsToHourMinuteSecond((uint)totalUsedTime, false)
                    };
                    paceDataList.Add(specialNode);
                }
            }
        }
    }
}
